#
# pymoz, Python extension for the Mozilla Toolkit
# Copyright (C) 2012 Malek Hadj-Ali
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#


################################################################################
# version check
################################################################################

import os
import platform

# include some const defined in pymoz/pymoz.mk
cwd = os.path.abspath(os.path.realpath(os.getcwd()))
exec(open(os.path.join(cwd, "pymoz", "pymoz.mk")).read())

if platform.python_version() < PYMOZ_TARGET_MINVERSION:
    raise SystemExit("Aborted: pymoz requires Python >= {}".format(PYMOZ_TARGET_MINVERSION))


################################################################################
# here we go
################################################################################

import argparse
import subprocess
import os.path
import shutil
import tarfile
import sys


#===============================================================================
# consts
#===============================================================================

project_name = "-".join((PYMOZ_NAME, PYMOZ_VERSION, PYMOZ_TARGET_MINVERSION))

app = "xulrunner"
app_version = "17.0.1"
app_name = "-".join((app, app_version))

base_url = "http://hg.mozilla.org/releases"
source_repo = "mozilla-release"
l10n_repo = "l10n/mozilla-release"

archive_ext = "tar.bz2"
hgtag = "FIREFOX_{}_RELEASE".format(app_version.replace(".", "_"))
archive_url = "{}/{{}}/archive/{}.{}".format(base_url, hgtag, archive_ext)
all_locales_url = "{}/{}/raw-file/{}/{}/locales/all-locales".format(base_url, source_repo, hgtag, app)

# folders/files names
source_name = "-".join((app, "source"))
build_name = "-".join((app, "build"))
l10n_name = "-".join((app, "l10n"))

# full paths
source_path = os.path.join(cwd, source_name)
build_path = os.path.join(cwd, build_name)
l10n_path = os.path.join(cwd, l10n_name)
all_locales_path = os.path.join(l10n_path, "all-locales")
source_archive = ".".join((source_path, archive_ext))
locales_build_path = os.path.join(build_path, "toolkit", "locales")
manifest_path = os.path.join(build_path, "dist", "bin", "chrome.manifest")
manifest_backup = os.path.join(cwd, "chrome.manifest")

# .mozconfig
mozconfig_str = """mk_add_options MOZ_OBJDIR={}
ac_add_options --with-l10n-base={}
ac_add_options --enable-application={}
ac_add_options --enable-optimize
ac_add_options --enable-chrome-format=flat
ac_add_options --enable-extensions=default,pymoz
ac_add_options --disable-tests
ac_add_options --disable-debug
ac_add_options --disable-debug-symbols"""

mozconfig_path = os.path.join(source_path, ".mozconfig")


def rmtree(path):
    for root, dirs, files in os.walk(path, topdown=False):
        for name in files:
            os.remove(os.path.join(root, name))
        for name in dirs:
            full_path = os.path.join(root, name)
            if os.path.islink(full_path):
                os.remove(full_path)
            else:
                os.rmdir(full_path)
    os.rmdir(path)


#===============================================================================
# class Builder
#===============================================================================

class Builder(argparse.Action):

    #---------------------------------------------------------------------------
    # utils
    #---------------------------------------------------------------------------

    def _run_cmd(self, cmd, cwd=cwd, env=None):
        print("\trunning: {}, in {}".format(cmd, cwd))
        return subprocess.check_call(cmd, cwd=cwd, env=env, shell=True)

    def _clean(self, path):
        if os.path.exists(path):
            print("Removing old {}...".format(path))
            if os.path.isfile(path):
                os.remove(path)
            elif os.path.isdir(path):
                rmtree(path)

    def _download(self, src, dst):
        self._clean(dst)
        print("Downloading {} to {}...".format(src, dst))
        self._run_cmd("wget -O {} {}".format(dst, src))

    def _extract(self, src, dst, path=cwd):
        self._clean(dst)
        print("Extracting {} to {}...".format(src, dst))
        tar = tarfile.open(name=src, mode="r")
        try:
            tmp = os.path.join(path, os.path.split(tar.next().name)[0])
            tar.extractall(path=path)
        except Exception:
            self._clean(tmp)
            raise
        else:
            print("\tmoving {} to {}...".format(tmp, dst))
            shutil.move(tmp, dst)
        finally:
            tar.close()

    def _mingw_path(self, path):
        return "/{}".format(path.replace(":", "").replace("\\", "/"))

    def _get_package_names(self, locale):
        plat = "-".join((sys.platform, platform.processor()))
        ext = archive_ext
        if os.name == "nt":
            if sys.maxsize > 2**32:
                plat = "-".join(("win64", platform.processor()))
            else:
                plat = sys.platform
            ext = "zip"
        old_name = ".".join((app_name, "en-US", plat, ext))
        new_name = ".".join((app_name, project_name, locale, plat, ext))
        return (os.path.join(build_path, "dist", old_name),
                os.path.join(cwd, new_name))

    def _get_all_locales(self):
        if not os.path.exists(l10n_path):
            os.mkdir(l10n_path)
        if not os.path.exists(all_locales_path):
            self._download(all_locales_url, all_locales_path)
        return sorted(open(all_locales_path, "r").read().strip().splitlines())

    def _update_all_locales(self, locales):
        open(all_locales_path, "w").write("\n".join(locales))

    def _build_locale(self, locale):
        locale_repo = "/".join((l10n_repo, locale))
        locale_path = os.path.join(l10n_path, locale)
        locale_archive = ".".join((locale_path, archive_ext))
        backup_manifest = open(manifest_path, "r").read()
        locale_dist_path = os.path.join(build_path, "dist", "bin", "chrome", locale)
        try:
            if not os.path.exists(locale_archive):
                self._download(archive_url.format(locale_repo), locale_archive)
            self._extract(locale_archive, locale_path, path=l10n_path)
            self._run_cmd("make chrome-{}".format(locale), cwd=locales_build_path)
        except Exception:
            self._clean(".".join((locale_dist_path, "manifest")))
            self._clean(locale_dist_path)
            open(manifest_path, "w").write(backup_manifest)
            self._clean(locale_path)
            self._clean(locale_archive)
            raise

    def _build_all_locales(self):
        locales = []
        for locale in self._get_all_locales():
            try:
                self._build_locale(locale)
            except Exception:
                pass
            else:
                locales.append(locale)
        self._update_all_locales(locales)

    def _distclean_all_locales(self):
        for locale in self._get_all_locales():
            locale_dist_path = os.path.join(build_path, "dist", "bin", "chrome", locale)
            self._clean(".".join((locale_dist_path, "manifest")))
            self._clean(locale_dist_path)

    #---------------------------------------------------------------------------
    # options
    #---------------------------------------------------------------------------

    def __call__(self, parser, namespace, values, option_string=None):
        if isinstance(option_string, str) and len(option_string) > 2:
            getattr(self, option_string[2:])(values)
        parser.exit()

    def clean(self, args):
        for path in (build_path, source_path, manifest_backup):
            self._clean(path)
        if os.path.exists(l10n_path):
            for locale in self._get_all_locales():
                self._clean(os.path.join(l10n_path, locale))

    def new(self, args):
        for path in (l10n_path, build_path, source_path, source_archive, manifest_backup):
            self._clean(path)
        self._download(archive_url.format(source_repo), source_archive)

    def setup(self, args):
        self.clean(args)
        self._extract(source_archive, source_path)
        print("Patching source...")
        for patch in sorted(os.listdir(os.path.join(cwd, "patches"))):
            self._run_cmd("patch -p0 < ../patches/{}".format(patch), cwd=source_path)
        print("Copying pymoz source...")
        shutil.copytree(os.path.join(cwd, "pymoz"),
                        os.path.join(source_path, "extensions", "pymoz"))
        print("Write config to {}...".format(mozconfig_path))
        if not os.path.exists(l10n_path):
            os.mkdir(l10n_path)
        if os.name == "nt":
            moz_objdir = self._mingw_path(build_path)
            l10n_base = self._mingw_path(l10n_path)
        else:
            moz_objdir = build_path
            l10n_base = l10n_path
        open(mozconfig_path, "w").write(mozconfig_str.format(moz_objdir, l10n_base, app))

    def build(self, args):
        if sys.executable:
            if os.name == "nt":
                pymoz_target = self._mingw_path(sys.executable)
            else:
                pymoz_target = sys.executable
            env = os.environ.copy()
            env["PYMOZ_TARGET"] = pymoz_target
            self._run_cmd("make -f client.mk build", cwd=source_path, env=env)
            shutil.copyfile(manifest_path, manifest_backup)
        else:
            raise SystemError("No Python executable!")

    def package(self, locale):
        if locale not in self._get_all_locales() + ["l10n", "en-US"]:
            raise argparse.ArgumentError(self, "invalid locale")
        self._distclean_all_locales()
        shutil.copyfile(manifest_backup, manifest_path) # reset to en-US
        if locale != "en-US":
            if locale == "l10n":
                self._build_all_locales()
            else:
                self._build_locale(locale)
        self._run_cmd("make package", cwd=build_path)
        shutil.copyfile(*self._get_package_names(locale))


#===============================================================================
# __main__
#===============================================================================

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--clean", nargs=0, action=Builder)
    group.add_argument("--new", nargs=0, action=Builder)
    group.add_argument("--setup", nargs=0, action=Builder)
    group.add_argument("--build", nargs=0, action=Builder)
    group.add_argument("--package", nargs="?", const="l10n", metavar="LOCALE", action=Builder)
    args = parser.parse_args()
